#include <iostream>
#include <cerrno>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "pthread.hpp"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;
using namespace mypthread;

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USE_ERROR
};

void useTips(char *command)
{
    cout << "useTips:" << command << " ip" << " port" << endl;
}

void sendmessage(int sockfd, struct sockaddr_in &destaddr, string name)
{
    string msg = "";
    while (msg != "q" && msg != "quit")
    {
        cout << name << ":please enter:";
        getline(cin, msg);
        int ret = sendto(sockfd, msg.c_str(), msg.size(), 0, (struct sockaddr *)&destaddr, sizeof(destaddr));
        if (ret == -1)
        {
            LOG(ERROR, "client: sendto is fatal");
        }
    }
}

void recvmessage(int sockfd, string name)
{
    // // 将接收到的消息和发送消息分开
    // // 方法一：open+write
    // // int fd = open("/dev/pts/6", O_WRONLY); // 将收到的消息输出到另一个窗口
    // char buf[1024] = {0};
    // while (strcmp(buf, "q") != 0 && strcmp(buf, "quit") != 0)
    // {
    //     sockaddr_in srcaddr;
    //     socklen_t len = sizeof(srcaddr);
    //     int ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&srcaddr, &len);
    //     if (ret > 0)
    //     {
    //         buf[ret] = '\n';
    //         buf[ret + 1] = '\0';

    //         // cout << name;
    //         // LOG(INFO, "%s:%d,recvmsg:%s", inet_ntoa(srcaddr.sin_addr), ntohs(srcaddr.sin_port), buf);

    //         // write(fd, buf, strlen(buf));
    //     }
    // }

    // 方法二
    char buf[1024] = {0};
    while (strcmp(buf, "q") != 0 && strcmp(buf, "quit") != 0)
    {
        sockaddr_in srcaddr;
        socklen_t len = sizeof(srcaddr);
        int ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&srcaddr, &len);
        if (ret > 0)
        {
            buf[ret] = '\0';
            //./udpclient 127.0.0.1 8899 2> /dev/pts/6，调用客户端的时候，使用该命令，将标准错误流输出重定向到/dev/pts/6设备
            fprintf(stderr, "[%s]%s\n", name.c_str(), buf); //我们仅仅将收到的消息输出到标准错误流，其他输入提示信息依然是标准输出流
        }
    }
}

int main(int argc, char *argv[])
{
    EnableScreen();
    if (argc != 3)
    {
        useTips(argv[0]);
        LOG(ERROR, "client: parameter usage error!");
        exit(USE_ERROR);
    }
    LOG(ERROR, "parameter usage success!");

    // 1、定义一个交流终端
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        LOG(FATAL, "client: creates socket fatally, errno:%d, errmsg:%s", errno, strerror(errno));
        exit(USE_ERROR);
    }
    LOG(FATAL, "client: creates socket successfully");

    // 2、用户端要不要将sockfd和自己的socket信息绑定呢
    // 一定要，但不是和server一样显示使用bind绑定（不建议也不推荐），
    // 什么时候绑定呢，如何绑定？当第一次发送数据的时候，OS会自动的给自己随机绑定port
    // 为什么呢？避免客户端port冲突，因为当你给你的客户端绑定某个端口号之后，另外的客户端也绑定了这个端口号，此时你主机上这两个服务就会造成冲突，无法确定网络中的唯一进程

    // 3、构建目标主机的socket信息
    struct sockaddr_in destaddr;
    memset(&destaddr, 0, sizeof(destaddr));
    destaddr.sin_family = AF_INET;
    destaddr.sin_port = htons(atoi(argv[2]));
    destaddr.sin_addr.s_addr = inet_addr(argv[1]);

    // 4 创建两个线程一个用于发送消息，一个用于接收消息
    Thread sendthread(bind(&sendmessage, sockfd, destaddr, placeholders::_1), "sendthread");
    Thread recvthread(bind(&recvmessage, sockfd, placeholders::_1), "recvthread");

    sendthread.join();
    recvthread.join();

    return 0;
}